package com.vst.orm.dao.jpa;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Query;

import com.vst.orm.search.ExampleOptions;
import com.vst.orm.search.Filter;
import com.vst.orm.search.ISearch;
import com.vst.orm.search.SearchResult;
import com.vst.orm.search.jpa.JPASearchProcessor;
import com.vst.orm.util.MetadataUtil;

public class JPABaseDAO
{
  private JPASearchProcessor searchProcessor;
  private EntityManager entityManager;
  
  public void setSearchProcessor(JPASearchProcessor searchProcessor)
  {
    this.searchProcessor = searchProcessor;
  }
  
  protected JPASearchProcessor getSearchProcessor()
  {
    return this.searchProcessor;
  }
  
  public void setEntityManager(EntityManager entityManager)
  {
    this.entityManager = entityManager;
  }
  
  protected EntityManager em()
  {
    return this.entityManager;
  }
  
  protected MetadataUtil getMetadataUtil()
  {
    return this.searchProcessor.getMetadataUtil();
  }
  
  protected void _persist(Object... entities)
  {
    for (Object entity : entities) {
      if (entity != null) {
        em().persist(entity);
      }
    }
  }
  
  protected boolean _removeById(Class<?> type, Serializable id)
  {
    if (id != null)
    {
      Query query = em().createQuery("select id from " + type.getName() + " where id = ?").setParameter(1, id);
      if (query.getResultList().size() != 0)
      {
        em().remove(em().getReference(type, id));
        return true;
      }
    }
    return false;
  }
  
  protected void _removeByIds(Class<?> type, Serializable... ids)
  {
    for (Object id : pullByIds("select id", type, ids)) {
      em().remove(em().getReference(type, id));
    }
  }
  
  protected boolean _removeEntity(Object entity)
  {
    if (entity != null)
    {
      if (em().contains(entity))
      {
        em().remove(entity);
        return true;
      }
      Serializable id = getMetadataUtil().getId(entity);
      return _removeById(entity.getClass(), id);
    }
    return false;
  }
  
  protected void _removeEntities(Object... entities)
  {
    for (Object entity : entities) {
      _removeEntity(entity);
    }
  }
  
  protected <T> T _find(Class<T> type, Serializable id)
  {
    return (T)em().find(type, id);
  }
  
  protected <T> T[] _find(Class<T> type, Serializable... ids)
  {
    Object[] retList = (Object[])Array.newInstance(type, ids.length);
    for (Object entity : pullByIds("select _it_", type, ids))
    {
      Serializable id = getMetadataUtil().getId(entity);
      for (int i = 0; i < ids.length; i++) {
        if (id.equals(ids[i])) {
          retList[i] = entity;
        }
      }
    }
    return (T[])retList;
  }
  
  protected <T> T _getReference(Class<T> type, Serializable id)
  {
    return (T)em().getReference(type, id);
  }
  
  protected <T> T[] _getReferences(Class<T> type, Serializable... ids)
  {
    T[] retList = (T[])Array.newInstance(type, ids.length);
    for (int i = 0; i < ids.length; i++) {
      retList[i] = _getReference(type, ids[i]);
    }
    return retList;
  }
  
  protected <T> List<T> _all(Class<T> type)
  {
    return em().createQuery("select _it_ from " + type.getName() + " _it_").getResultList();
  }
  
  protected <T> T _merge(T entity)
  {
    return (T)em().merge(entity);
  }
  
  protected <T> T[] _merge(Class<T> arrayType, T... entities)
  {
    T[] retList = (T[])Array.newInstance(arrayType, entities.length);
    for (int i = 0; i < entities.length; i++) {
      retList[i] = _merge(entities[i]);
    }
    return retList;
  }
  
  protected <T> T _persistOrMerge(T entity)
  {
    if (entity == null) {
      return null;
    }
    if (em().contains(entity)) {
      return entity;
    }
    Serializable id = getMetadataUtil().getId(entity);
    if (!validId(id))
    {
      _persist(new Object[] { entity });
      return entity;
    }
    T prev = (T) em().find(entity.getClass(), id);
    if (prev == null)
    {
      _persist(new Object[] { entity });
      return entity;
    }
    return (T)_merge(entity);
  }
  
  protected <T> T[] _persistOrMerge(Class<T> arrayType, T... entities)
  {
    T[] retList = (T[])Array.newInstance(arrayType, entities.length);
    for (int i = 0; i < entities.length; i++) {
      retList[i] = _persistOrMerge(entities[i]);
    }
    return retList;
  }
  
  protected List _search(ISearch search)
  {
    if (search == null) {
      throw new NullPointerException("Search is null.");
    }
    if (search.getSearchClass() == null) {
      throw new NullPointerException("Search class is null.");
    }
    return getSearchProcessor().search(em(), search);
  }
  
  protected List _search(Class<?> searchClass, ISearch search)
  {
    if (search == null) {
      throw new NullPointerException("Search is null.");
    }
    if (searchClass == null) {
      throw new NullPointerException("Search class is null.");
    }
    if ((search.getSearchClass() != null) && (!search.getSearchClass().equals(searchClass))) {
      throw new IllegalArgumentException("Search class does not match expected type: " + searchClass.getName());
    }
    return getSearchProcessor().search(em(), searchClass, search);
  }
  
  protected int _count(ISearch search)
  {
    if (search == null) {
      throw new NullPointerException("Search is null.");
    }
    if (search.getSearchClass() == null) {
      throw new NullPointerException("Search class is null.");
    }
    return getSearchProcessor().count(em(), search);
  }
  
  protected int _count(Class<?> searchClass, ISearch search)
  {
    if (search == null) {
      throw new NullPointerException("Search is null.");
    }
    if (searchClass == null) {
      throw new NullPointerException("Search class is null.");
    }
    if ((search.getSearchClass() != null) && (!search.getSearchClass().equals(searchClass))) {
      throw new IllegalArgumentException("Search class does not match expected type: " + searchClass.getName());
    }
    return getSearchProcessor().count(em(), searchClass, search);
  }
  
  protected int _count(Class<?> type)
  {
    return ((Number)em().createQuery("select count(*) from " + type.getName()).getSingleResult()).intValue();
  }
  
  protected SearchResult _searchAndCount(ISearch search)
  {
    if (search == null) {
      throw new NullPointerException("Search is null.");
    }
    if (search.getSearchClass() == null) {
      throw new NullPointerException("Search class is null.");
    }
    return getSearchProcessor().searchAndCount(em(), search);
  }
  
  protected SearchResult _searchAndCount(Class<?> searchClass, ISearch search)
  {
    if (search == null) {
      throw new NullPointerException("Search is null.");
    }
    if (searchClass == null) {
      throw new NullPointerException("Search class is null.");
    }
    if ((search.getSearchClass() != null) && (!search.getSearchClass().equals(searchClass))) {
      throw new IllegalArgumentException("Search class does not match expected type: " + searchClass.getName());
    }
    return getSearchProcessor().searchAndCount(em(), searchClass, search);
  }
  
  protected Object _searchUnique(ISearch search)
    throws NonUniqueResultException, NoResultException
  {
    if (search == null) {
      throw new NullPointerException("Search is null.");
    }
    if (search.getSearchClass() == null) {
      throw new NullPointerException("Search class is null.");
    }
    return getSearchProcessor().searchUnique(em(), search);
  }
  
  protected Object _searchUnique(Class<?> searchClass, ISearch search)
    throws NonUniqueResultException, NoResultException
  {
    if (search == null) {
      throw new NullPointerException("Search is null.");
    }
    if (searchClass == null) {
      throw new NullPointerException("Search class is null.");
    }
    if ((search.getSearchClass() != null) && (!search.getSearchClass().equals(searchClass))) {
      throw new IllegalArgumentException("Search class does not match expected type: " + searchClass.getName());
    }
    return getSearchProcessor().searchUnique(em(), searchClass, search);
  }
  
  protected boolean _contains(Object o)
  {
    return em().contains(o);
  }
  
  protected void _flush()
  {
    em().flush();
  }
  
  protected void _refresh(Object... entities)
  {
    for (Object entity : entities) {
      if (entity != null) {
        em().refresh(entity);
      }
    }
  }
  
  protected boolean _exists(Object entity)
  {
    if (entity == null) {
      return false;
    }
    if (em().contains(entity)) {
      return true;
    }
    return _exists(entity.getClass(), getMetadataUtil().getId(entity));
  }
  
  protected boolean _exists(Class<?> type, Serializable id)
  {
    if (type == null) {
      throw new NullPointerException("Type is null.");
    }
    if (!validId(id)) {
      return false;
    }
    Query query = em().createQuery("select id from " + type.getName() + " where id = :id");
    query.setParameter("id", id);
    return query.getResultList().size() == 1;
  }
  
  protected boolean[] _exists(Class<?> type, Serializable... ids)
  {
    if (type == null) {
      throw new NullPointerException("Type is null.");
    }
    boolean[] ret = new boolean[ids.length];
    for (Object id : pullByIds("select id", type, ids)) {
      for (int i = 0; i < ids.length; i++) {
        if (id.equals(ids[i])) {
          ret[i] = true;
        }
      }
    }
    return ret;
  }
  
  protected Filter _getFilterFromExample(Object example)
  {
    return this.searchProcessor.getFilterFromExample(example);
  }
  
  protected Filter _getFilterFromExample(Object example, ExampleOptions options)
  {
    return this.searchProcessor.getFilterFromExample(example, options);
  }
  
  private List<?> pullByIds(String select, Class<?> type, Serializable[] ids)
  {
    List<Serializable> nonNulls = new LinkedList();
    
    StringBuilder sb = new StringBuilder(select);
    sb.append(" from ");
    sb.append(type.getName());
    sb.append(" _it_ where ");
    for (Serializable id : ids) {
      if (id != null)
      {
        if (nonNulls.size() == 0) {
          sb.append("id = ?");
        } else {
          sb.append(" or id = ?");
        }
        nonNulls.add(id);
      }
    }
    if (nonNulls.size() == 0) {
      return new ArrayList(0);
    }
    Query query = em().createQuery(sb.toString());
    int idx = 1;
    for (Serializable id : nonNulls) {
      query.setParameter(idx++, id);
    }
    return query.getResultList();
  }
  
  private boolean validId(Serializable id)
  {
    if (id == null) {
      return false;
    }
    if (((id instanceof Number)) && (((Number)id).equals(Integer.valueOf(0)))) {
      return false;
    }
    if (((id instanceof String)) && ("".equals(id))) {
      return false;
    }
    return true;
  }
}

